﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Linq;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.Linkbase
{
    /// <summary>
    /// XBRL 2.1 Specification 3.5.3.9
    /// The base class arc.
    /// All XBRL extended links MAY contain arcs. Arcs document relationships between resources identified 
    /// by locators in extended links or occurring as resources in extended links.
    /// </summary>
    public class Arc : IIndexable
    {
        int IIndexable.Id
        {
            get { return GetHashCode(); }
        }

        int IIndexable.Name
        {
            get { return GetHashCode(); }
        }

        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private XElement arcElement;

        /// <summary>
        /// The XElement of arc.
        /// </summary>
        public XElement ArcElement
        {
            get { return arcElement; }
            set { arcElement = value; }
        }

        private ExtendedLink extendedLinkParent;

        /// <summary>
        /// The Extended Link Parent of arc.
        /// </summary>
        public ExtendedLink ExtendedLinkParent
        {
            get { return extendedLinkParent; }
            set { extendedLinkParent = value; }
        }

        private string localName;

        /// <summary>
        /// The local name of arc.
        /// </summary>
        public string LocalName
        {
            get { return localName; }
            set { localName = value; }
        }

        private string namespaceUri;

        /// <summary>
        /// The namespace of arc.
        /// </summary>
        public string NamespaceUri
        {
            get { return namespaceUri; }
            set { namespaceUri = value; }
        }

        private decimal weight; 

        /// <summary>
        /// Weight attribute for calculation arcs (Spec. 3.5.3.9.7.2)
        /// </summary>
        public decimal Weight
        {
            get { return weight; }
            set { weight = value; }
        }

        private decimal order;

        /// <summary>
        /// Order attribute (Spec. 3.5.3.9.5)
        /// </summary>
        public decimal Order
        {
            get { return order; }
            set { order = value; }
        }

        private int priority;

        /// <summary>
        /// priority attribute (Spec. 3.5.3.9.7.2)
        /// </summary>
        public int Priority
        {
            get { return priority; }
            set { priority = value; }
        }

        private string title;

        /// <summary>
        /// The title of this arc.
        /// </summary>
        public string Title
        {
            get
            {
                return title;
            }
            set
            {
                title = value;
            }
        }

        private string fromId;

        /// <summary>
        /// The identifier of the "from" portion of the arc.
        /// </summary>
        public string FromId
        {
            get
            {
                return fromId;
            }
            set
            {
                fromId = value;
            }
        }

        private string toId;

        /// <summary>
        /// The identifier of the "to" portion of the arc.
        /// </summary>
        public string ToId
        {
            get
            {
                return toId;
            }
            set
            {
                toId = value;
            }
        }

        private Uri arcRole;

        /// <summary>
        /// The identifier of the "arcrole" portion of the arc.
        /// </summary>
        public Uri ArcRole
        {
            get
            {
                return arcRole;
            }
            set
            {
                arcRole = value;
            }
        }

        private String extendedLinkRole;

        /// <summary>
        /// The identifier of the "Extended Link Role" portion of the link.
        /// </summary>
        /// <value>
        /// The extended link role.
        /// </value>
        public string ExtendedLinkRole
        {
            get
            {
                return extendedLinkRole;
            }
            set
            {
                extendedLinkRole = value;
            }
        }

        private Locator fromLocator;

        /// <summary>
        /// The locator of the "from" portion of the arc.
        /// </summary>
        public Locator FromLocator
        {
            get
            {
                return fromLocator;
            }
            set
            {
                fromLocator = value;
            }
        }

        private Locator toLocator;

        /// <summary>
        /// The locator of the "to" portion of the arc.
        /// </summary>
        public Locator ToLocator
        {
            get
            {
                return toLocator;
            }
            set
            {
                toLocator = value;
            }
        }

        private Resource toResource;

        /// <summary>
        /// The resource of the "to" portion of the arc.
        /// </summary>
        public Resource ToResource
        {
            get { return toResource; }
            set { toResource = value; }
        }

        // An arc MAY reference multiple XML fragments on each side (“from” and “to”) of the arc. 
        // This can occur if there are multiple locators and/or resources in the extended link with the same 
        // xlink:label attribute value identified by the xlink:from or xlink:to attribute of the arc. 

        /// <summary>
        /// A collection of locators referenced by the "from" label referenced in the calculation arc.
        /// </summary>
        public List<Locator> FromLocators
        {
            get;
            private set;
        }

        /// <summary>
        /// A collection of locators referenced by the "to" label referenced in the calculation arc.
        /// </summary>
        public List<Locator> ToLocators
        {
            get;
            private set;
        }

        // use attribute (Spec. 3.5.3.9.7.1)
        // The optional use attribute MUST take one of two possible values – "optional", or "prohibited".
        private string useAttribute;    
     
        /// <returns> Content of attribute "use". </returns>
        public virtual string UseAttribute
        {
            get
            {
                return useAttribute;
            }
        }

        private IEnumerable<XAttribute> attributes;

        /// <returns> A list of attribute. </returns>
        public IEnumerable<XAttribute> Attributes
        {
            get { return attributes; }
            set { attributes = value; }
        }


        // Properties to dimensional taxonomy.

        private Uri targetRole;

        /// <returns> targetRole of this hypercube. </returns>
        public Uri TargetRole
        {
            get { return targetRole; }
            set { targetRole = value; }
        }

        private string contextElement;

        /// <returns> contextElement of this hypercube. </returns>
        public string ContextElement
        {
            get { return contextElement; }
            set { contextElement = value; }
        }

        private string summable;

        /// <returns> summable of this hypercube. </returns>
        public string Summable
        {
            get { return summable; }
            set { summable = value; }
        }

        private string usable;

        /// <returns> usable of this hypercube. </returns>
        public string Usable
        {
            get { return usable; }
            set { usable = value; }
        }

        private string closed;

        /// <returns> closed of this hypercube. </returns>
        public string Closed
        {
            get { return closed; }
            set { closed = value; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="arcElement">
        ///            The node of   Arc element. </param>
        /// <param name="extendedLink">
        ///            Extended link role of the arc. </param>///            
        internal Arc(XElement arcElement, ExtendedLink extendedLink)
        {
            this.arcElement = arcElement;
            extendedLinkParent = extendedLink;

            this.ToLocators = new List<Locator>();
            this.FromLocators = new List<Locator>();

            localName = arcElement.Name.LocalName;
            namespaceUri = arcElement.Name.NamespaceName;
            this.extendedLinkRole = extendedLink.ExtendedLinkRole.AbsoluteUri;

            useAttribute = "optional";  // the default of the use attribute is "optional" (Spec. 3.5.3.9.7.1)
            priority = 0;  // default of the priority attribute is "0". (Spec. 3.5.3.9.7.2)						
            weight = 0;
            order = 0;

            // HACK verificar uso
            attributes = arcElement.Attributes();

            foreach (XAttribute currentAttribute in arcElement.Attributes())
            {
                if (currentAttribute.Name.LocalName.Equals("arcrole"))
                {
                    try
                    {
                        this.ArcRole = new Uri(currentAttribute.Value);
                    }
                    catch (Exception ex)
                    {
                        log.Error("Validation error on Uri:", ex);
                        this.extendedLinkParent.ParentDocument.Dts.ValidatedLinkbases = false;
                    }
                }

                else if (currentAttribute.Name.LocalName.Equals("title"))
                    this.Title = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("from"))
                    this.FromId = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("to"))
                    this.ToId = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("order"))
                    this.Order = Decimal.Parse(currentAttribute.Value);
                else if (currentAttribute.Name.LocalName.Equals("weight"))
                    this.Weight = Decimal.Parse(currentAttribute.Value);
                else if (currentAttribute.Name.LocalName.Equals("use"))
                    this.useAttribute = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("priority"))
                    this.priority = Int32.Parse(currentAttribute.Value);

                // dimensional attributes
                else if (currentAttribute.Name.LocalName.Equals("targetRole"))
                {
                    try
                    {
                        this.targetRole = new Uri(currentAttribute.Value);
                    }
                    catch (Exception ex)
                    {
                        log.Error("Validation error on Uri:", ex);
                        this.extendedLinkParent.ParentDocument.Dts.ValidatedLinkbases = false;
                    }
                }

                else if (currentAttribute.Name.LocalName.Equals("summable"))
                    this.summable = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("usable"))
                    this.usable = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("closed"))
                    this.closed = currentAttribute.Value;
                else if (currentAttribute.Name.LocalName.Equals("contextElement"))
                    this.contextElement = currentAttribute.Value;
  
            }

            //log.Debug("Creating arc: " + extendedLinkRole + "-" + localName + " arcRole: " + arcRole + " from: " + fromId + " to: " + toId);

            ResolveToLocators();
        }

        private void ResolveToLocators()
        {
            foreach (Locator currentLocator in extendedLinkParent.Locators)
            {
                if (this.fromId.Equals(currentLocator.Label))
                {
                    this.FromLocators.Add(currentLocator);
                }

                if (this.ToId.Equals(currentLocator.Label))
                {
                    this.ToLocators.Add(currentLocator);
                }
            }
        }


        /// <summary>
        /// This method checks if this arc is overridden by another arc.
        /// </summary>
        /// <param name="overridingArc">
        ///            Possible overriding arc. </param>
        /// <returns> True if overriding arc overrides this arc, otherwise false. The
        ///         implementation follows the Spec. chapter 3.5.3.9.7.4 and
        ///         3.5.3.9.7.5 </returns>
        public virtual bool IsOverriddenByArc(Arc overridingArc)
        {
            //if (overridingArc.ToLocator == null || ToLocator == null)
            //{
            //    return false; // is resource: label or reference
            //}

            if (overridingArc.ToLocator != null && ToLocator != null)
            {
                // a locator is overridden 
                if (overridingArc.Priority >= priority && IsEquivalentRelationship(overridingArc))
                {
                    return true;
                }
            }
            else if (overridingArc.ToResource != null && ToResource != null)
            {
                // a resource is overridden
                if (overridingArc.Priority >= priority && IsEquivalentRelationship(overridingArc))
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// This method checks if this arc is prohibited by another arc.
        /// </summary>
        /// <param name="prohibitingArc">
        ///            Possible prohibiting arc. </param>
        /// <returns> True if prohibitingArc prohibits this arc, otherwise false. The
        ///         implementation follows the Spec. chapter 3.5.3.9.7.4 and
        ///         3.5.3.9.7.5 </returns>
        public virtual bool IsProhibitedByArc(Arc prohibitingArc)
        {
            if (prohibitingArc.UseAttribute.Equals("prohibited") && IsEquivalentRelationship(prohibitingArc) && prohibitingArc.Priority >= priority)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// This method checks if this arc prohibits another one.
        /// </summary>
        /// <param name="prohibitedArc">
        ///            Arc which is probably prohibited by this arc. </param>
        /// <returns> True if this arc prohibits prohibitedArc, otherwise false. The
        ///         implementation follows the Spec. chapter 3.5.3.9.7.4 and
        ///         3.5.3.9.7.5 </returns>
        public virtual bool ProhibitsArc(Arc prohibitedArc)
        {
            if (useAttribute.Equals("prohibited") && IsEquivalentRelationship(prohibitedArc) && prohibitedArc.Priority <= priority)
            {
                return true;
            }
            return false;
        }


        /// <summary>
        /// Checks whether two arcs form an "Equivalent Relationship" as described in
        /// the Spec. chapter 3.5.3.9.7.4.
        /// </summary>
        /// <param name="equivalentArc">
        ///            Arc against the relationship is checked. </param>
        /// <returns> True if both arcs (this one and equivalentArc) from an
        ///         "Equivalent Relationship" as described in chapter 3.5.2.9.7.4 in
        ///         the Spec. </returns>
        public virtual bool IsEquivalentRelationship(Arc equivalentArc)
        {
            return EqualSourceTarget(equivalentArc) && HasSameAttributes(equivalentArc);
        }


        /// <summary>
        /// Checks if two Arc objects have the same source and the same target
        /// element. This is true if and only if: <br/>- the source locators of both
        /// arcs refer to the same concept <br/>- the target locators of both arcs
        /// refer to the same concept <br/>- both arcs are in the same extended link
        /// role
        /// </summary>
        /// <param name="arcToCompare">
        ///            The object the current Arc is checked against. </param>
        /// <returns> True if the check succeeds, false otherwise. </returns>
        public virtual bool EqualSourceTarget(Arc arcToCompare)
        {
            if (this == arcToCompare)
            {
                return true;
            }

            //if (ToLocator == null || arcToCompare.ToLocator == null)
            //{
            //    return false; //is resource: label or reference
            //}

            if (ToLocator != null && arcToCompare.ToLocator != null)
            {
                return FromLocator.HrefResourceId.Equals(arcToCompare.FromLocator.HrefResourceId) &&
                       ToLocator.HrefResourceId.Equals(arcToCompare.ToLocator.HrefResourceId) &&
                       extendedLinkRole.Equals(arcToCompare.ExtendedLinkRole);
            }

            if (ToResource != null && arcToCompare.ToResource != null)
            {
                return FromLocator.HrefResourceId.Equals(arcToCompare.FromLocator.HrefResourceId) &&
                       ToResource.Label.Equals(arcToCompare.ToResource.Label) &&
                       extendedLinkRole.Equals(arcToCompare.ExtendedLinkRole);
            }

            return false;
        }


        /// <summary>
        /// Checks whether two arcs have s-equal non-XLink attributes (except "use"
        /// and "priority"), which is one requirement to be of equivalent
        /// relationship as described in Spec. chapter 3.5.3.9.7.4.
        /// </summary>
        /// <param name="arcToCompare">
        ///            Arc which attributes are compared to the attributes of this
        ///            arc. </param>
        /// <returns> True if the value of any non-XLink attribute except "use" and
        ///         "priority" is s-equal to the same non-XLink attribute on the
        ///         other arc. </returns>
        public virtual bool HasSameAttributes(Arc arcToCompare)
        {
            IEnumerable<XAttribute> attributesToCompare = arcToCompare.Attributes;
            attributesToCompare.Where(a => a.Name.LocalName.Equals("use") || a.Name.LocalName.Equals("priority")).Remove();
            attributesToCompare.Where(a => a.Name.NamespaceName.Equals("http://www.w3.org/1999/xlink") || a.Name.NamespaceName.Equals("http://www.w3.org/2000/xmlns")).Remove();
            attributesToCompare.Where(a => a.IsNamespaceDeclaration).Remove();
            List<XAttribute> listAtributesToCompare = attributesToCompare.ToList();

            IEnumerable<XAttribute> thisArcAttributes = arcElement.Attributes();
            thisArcAttributes.Where(a => a.Name.LocalName.Equals("use") || a.Name.LocalName.Equals("priority")).Remove();
            thisArcAttributes.Where(a => a.Name.NamespaceName.Equals("http://www.w3.org/1999/xlink") || a.Name.NamespaceName.Equals("http://www.w3.org/2000/xmlns")).Remove();
            thisArcAttributes.Where(a => a.IsNamespaceDeclaration).Remove();
            List<XAttribute> thisListAttributes = thisArcAttributes.ToList();            

            // Specification 3.5.3.9.5: If missing, the order attribute value MUST default to "1". 
            XAttribute orderAttr = new XAttribute("order", "1");

            if (IsMissingOrderAttribute(thisArcAttributes))
            {
                thisListAttributes.Add(orderAttr);
            }

            if (IsMissingOrderAttribute(attributesToCompare))
            {
                listAtributesToCompare.Add(orderAttr);
            }

            if (thisListAttributes.Count() != listAtributesToCompare.Count())
            {
                return false;
            }

            foreach (XAttribute currAttr in thisListAttributes)
            {
                string currAttrValue = NormalizeNumericValue(currAttr);

                //there must be an s-equal attribute in the other arc, see Spec. 3.5.3.9.7.4
                foreach (XAttribute currAttrToCompare in listAtributesToCompare)
                {
                    string currAttrToCompareValue = NormalizeNumericValue(currAttrToCompare);

                    if (currAttr.Name.LocalName.Equals(currAttrToCompare.Name.LocalName) && 
                        currAttrValue.Equals(currAttrToCompareValue) &&
                        currAttr.Name.NamespaceName.Equals(currAttrToCompare.Name.NamespaceName))
                    {
                        goto nextAttribute;
                    }
                }

                return false;  // the currAttr is different

            nextAttribute: ;
            }

            return true;
        }


        private bool IsMissingOrderAttribute(IEnumerable<XAttribute> attributesToCompare)
        {
            foreach (XAttribute currAttr in attributesToCompare)
            {
                if (currAttr.Name.LocalName.Equals("order"))
                    return false;
            }

            return true;
        }


        private string NormalizeNumericValue(XAttribute attrToNormalize)
        {
            string currAttrValue = attrToNormalize.Value;

            if (!attrToNormalize.Name.NamespaceName.Equals("http://www.xbrl.org/2003/linkbase"))
            {
                if (attrToNormalize.GetSchemaInfo() != null)
                {
                    if (attrToNormalize.GetSchemaInfo().SchemaType != null)
                    {
                        switch (attrToNormalize.GetSchemaInfo().SchemaType.TypeCode)
                        {
                            case XmlTypeCode.Decimal:
                                currAttrValue = Decimal.Parse(attrToNormalize.Value).ToString();
                                break;
                            case XmlTypeCode.Double:
                                currAttrValue = Double.Parse(attrToNormalize.Value).ToString();
                                break;
                            case XmlTypeCode.Float:
                                currAttrValue = float.Parse(attrToNormalize.Value).ToString();
                                break;
                            case XmlTypeCode.Integer:
                                currAttrValue = Int32.Parse(attrToNormalize.Value).ToString();
                                break;
                            case XmlTypeCode.Boolean:
                                if (attrToNormalize.Value.Equals("1"))
                                {
                                    currAttrValue = "true";
                                }
                                if (attrToNormalize.Value.Equals("0"))
                                {
                                    currAttrValue = "false";
                                }
                                break;
                        }
                    }
                }
            }

            return currAttrValue;
        }


        public override int GetHashCode()
        {
            int hash = 1;
            hash = hash * 31 + extendedLinkRole.GetHashCode();
            hash = hash * 31 + arcElement.Name.GetHashCode();
            hash = hash * 31 + arcRole.GetHashCode();
            hash = hash * 31 + toId.GetHashCode();
            hash = hash * 31 + fromId.GetHashCode();
            return hash;
        }

    }
}
